<template>
  <div class="hello">
    <div class="canvas" ref="threejsContainer"></div>

    <!-- 加载模型的按钮 -->
    <button @click="loadModel">加载模型</button>
    <!-- 控制模型的按钮 -->
    <button @click="increaseScale">增大尺寸</button>
    <button @click="decreaseScale">减小尺寸</button>
    <button @click="moveUp">上移</button>
    <button @click="moveDown">下移</button>
    <button @click="rotateX">绕X轴旋转</button>
    <button @click="rotateY">绕Y轴旋转</button>
    <button @click="rotateZ">绕Z轴旋转</button>
    <button @click="guandao">光道</button>
  </div>
</template>

<script>
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

//可以使用Three.js的 OrbitControls 控件来允许用户自由地旋转和缩放场景，从而更容易地从不同角度观察模型。
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

// 确保这里的路径与您放置模型的实际路径相匹配
import demoModel from '@/assets/models/demo.gltf';

let scene =  null; // 创建场景
let camera = null; //相机
let renderer = null; //渲染器
let container = null; //DOM
let controls = null; // 控制器目标点（场景中心）

export default {
  name: 'ThreeJsComponent',
  data() {
    return {
      key: 0,

      model: null, // 添加一个属性来保存模型的引用
      cylinderModel: null,
      cubeModel: null,
    };
  },
  props: {
    msg: String
  },
  mounted() {
    this.initThreeJS();
  },
  methods: {
    initThreeJS() {
      container = this.$refs.threejsContainer; //DOM

      // 创建场景
      scene = new THREE.Scene();

      // 创建相机
      camera = new THREE.PerspectiveCamera(75, container.offsetWidth / container.offsetHeight, 0.1, 1000);
      // 设置相机的位置
      camera.position.set(0, 800, 800);  //camera.position.set(x, y, z);  //camera.position.z = 5; //单独控制z轴
      camera.lookAt(new THREE.Vector3(0, 0, 0)); // 让摄像机朝向原点

      // 设置相机朝向场景的中心点（或者任何感兴趣的点）
      camera.lookAt(scene.position);

      // 添加辅助工具
      /* const axesHelper = new THREE.AxesHelper(5);
      scene.add(axesHelper);

      const gridHelper = new THREE.GridHelper(10, 10);
      scene.add(gridHelper); */

      // 创建渲染器
      renderer = new THREE.WebGLRenderer();
      renderer.setSize(container.offsetWidth, container.offsetHeight);
      container.appendChild(renderer.domElement);

      // 添加OrbitControls
      controls = new OrbitControls(camera, renderer.domElement);
      controls.target.set(0, 0, 0); // 控制器目标点（场景中心）

      // 添加环境光
      const ambientLight = new THREE.AmbientLight(0xf0f5ff, 1);
      scene.add(ambientLight);

      // 添加方向光
      const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
      directionalLight.position.set(5, 10, 5); // 可以调整位置以改变光线方向
      directionalLight.castShadow = true; // 允许光源产生阴影
      scene.add(directionalLight);
      renderer.shadowMap.enabled = true;// 在渲染器上启用阴影映射
      renderer.shadowMap.enabled = true;
      renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 软阴影效果
      //光源的阴影摄像机范围
      directionalLight.shadow.camera.left = -500;
      directionalLight.shadow.camera.right = 500;
      directionalLight.shadow.camera.top = 500;
      directionalLight.shadow.camera.bottom = -500;
      directionalLight.shadow.camera.near = 0.5;
      directionalLight.shadow.camera.far = 1000;
      //阴影贴图分辨率 请注意，这会增加GPU的负担 增加阴影映射精度
      directionalLight.shadow.mapSize.width = 2048; // 默认是 512
      directionalLight.shadow.mapSize.height = 2048; // 默认是 512

      scene.background = new THREE.Color(0xaaaaaa); // 设置一个较亮的背景色

      camera.near = 0.1;
      camera.far = 10000; //距离之后的对象将不会被渲染
      camera.updateProjectionMatrix();

      // 加载GLTF模型 .gltf
      /* const loader = new GLTFLoader();
      loader.load(demoModel, (gltf) => {
        let box = gltf.scene;
        box.traverse((child) => {
          if (child.isMesh) {
            // 为模型的每个部分应用一个基础材质
            child.material = new THREE.MeshStandardMaterial({
              color: 0xffffff, // 使用较亮的颜色
              metalness: 0.5,  // 调整金属感
              roughness: 0.4,  // 调整粗糙度
            });

            // 设置模型的子网格能够投射和接收阴影
            child.castShadow = true;    // 允许网格（模型）投射阴影
            child.receiveShadow = true; // 允许网格（模型）接收阴影
          }
        });
        box.position.y += 1;
        scene.add(box);
        this.model = box; // 保存模型引用
      }, undefined, function (error) {
        console.error(error);
      }); */


      // 创建一个立方体，并设置它投射阴影
      /* const cubeGeometry = new THREE.BoxGeometry(100, 100, 100);
      const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
      const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
      cube.position.set(0, 51, 0);
      cube.castShadow = true; // 允许立方体投射阴影
      scene.add(cube); */

      // 动画循环
      const animate = () => {
        /* this.key ++;
        console.log(this.key); */
        requestAnimationFrame(animate);
        controls.update(); // 更新控制器 只有在使用阻尼或自动旋转时才需要
        renderer.render(scene, camera);
      }
      animate();
      this.wangGeXian();
      this.groundFun();
    },
    //网格线 GridHelper对象在Three.js中是用于辅助视觉参考的，并不是一个实际的“物理”对象，它本身不接受阴影
    wangGeXian() {
      // 定义网格大小和网格间距
      const size = 5000; // 网格总大小
      const divisions = 100; // 网格将被细分的数量

      // 创建一个网格辅助对象，第一个参数是网格大小，第二个参数是网格中分割的数量
      // 第三个和第四个参数（可选）分别定义了网格线的颜色
      const gridHelper = new THREE.GridHelper(size, divisions, 0x52FF00, 0x999999);
      gridHelper.position.y = 1;  // 将网格辅助对象在Y轴方向上移动12单位
      // 将网格辅助对象添加到场景中
      scene.add(gridHelper);
    },
    //地面
    groundFun() {
      // 创建平面几何体，用作地面
      const planeGeometry = new THREE.PlaneGeometry(5000, 5000);
      // 创建材质
      const planeMaterial = new THREE.MeshStandardMaterial({
        color: 0xFFFFFF, // 地面颜色
        side: THREE.DoubleSide // 确保地面的两面都可见
      });

      // 创建Mesh对象
      const plane = new THREE.Mesh(planeGeometry, planeMaterial);
      plane.receiveShadow = true; // 允许地面接收阴影
      plane.rotation.x = -Math.PI / 2; // 旋转平面使其水平
      plane.position.y = 0; // 设置地面位置（可根据需要调整）

      // 将创建的地面Mesh添加到场景中
      scene.add(plane);
    },

    loadModel() {
      // 检查模型是否已经被加载
      if (this.cylinderModel || this.cubeModel) {
        console.log("模型已经加载");
        return;
      }


      const loader = new GLTFLoader();
      const crystalMaterial = this.createCrystalMaterial(); // 创建水晶材质

      loader.load(demoModel, (gltf) => {
        let cylinderModel = null; // 圆柱体模型
        let cubeModel = null;     // 立方体模型
        gltf.scene.traverse((child) => {
          if (child.isMesh) {
            //child.material = new THREE.MeshStandardMaterial({ color: 0x00ff30 });
            child.material = crystalMaterial; // 应用水晶材质

            // 根据名称区分模型
            if (child.name === '圆柱体') {
              cylinderModel = child;
              child.material = new THREE.MeshStandardMaterial({
                color: 0xff0080, // 使用较亮的颜色
                metalness: 0.5,  // 调整金属感
                roughness: 0.4,  // 调整粗糙度
              });
              cylinderModel.position.y += 1;
            } else if (child.name === '立方体') {
              cubeModel = child;
              child.material = new THREE.MeshStandardMaterial({ color: 0x4f87ff }); // 蓝色材质
              cubeModel.position.y += 1;
            }
            child.castShadow = true; // 使模型能够投射阴影
            child.receiveShadow = true; // 使模型能够接收阴影
          }
        });

        if (cylinderModel && cubeModel) {
          scene.add(cylinderModel);
          scene.add(cubeModel);
          this.cylinderModel = cylinderModel; // 保存圆柱体模型引用
          this.cubeModel = cubeModel;         // 保存立方体模型引用
        }
      }, undefined, function (error) {
        console.error(error);
      });
    },
    //增大尺寸
    increaseScale() {
      if (this.cylinderModel) {
        this.cylinderModel.scale.x *= 1.1;
        this.cylinderModel.scale.y *= 1.1;
        this.cylinderModel.scale.z *= 1.1;
      }
    },
    //减小尺寸
    decreaseScale() {
      if (this.cylinderModel) {
        this.cylinderModel.scale.x /= 1.1;
        this.cylinderModel.scale.y /= 1.1;
        this.cylinderModel.scale.z /= 1.1;
      }
    },
    //上移
    moveUp() {
      if (this.cylinderModel) {
        this.cylinderModel.position.y += 2;
      }
    },
    //下移
    moveDown() {
      if (this.cylinderModel) {
        this.cylinderModel.position.y -= 2;
      }
    },
    // 绕X轴旋转
    rotateX() {
      if (this.cylinderModel) {
        // 增加模型在X轴的旋转角度
        this.cylinderModel.rotation.x += Math.PI / 18; // 旋转10度（Math.PI / 18 弧度）
      }
    },

    // 绕Y轴旋转
    rotateY() {
      if (this.cylinderModel) {
        // 增加模型在Y轴的旋转角度
        this.cylinderModel.rotation.y += Math.PI / 18; // 旋转10度
      }
    },

    // 绕Z轴旋转
    rotateZ() {
      if (this.cylinderModel) {
        // 增加模型在Z轴的旋转角度
        this.cylinderModel.rotation.z += Math.PI / 18; // 旋转10度
      }
    },
    //透明材质
    createCrystalMaterial() {
      return new THREE.MeshPhysicalMaterial({
        color: 0x00ff30,
        transparency: 0.9,
        transparent: true,
        roughness: 0,
        metalness: 0,
        reflectivity: 0.5,
        clearcoat: 0.5,
        clearcoatRoughness: 0
      });
    },
  guandao() {
    // 定义流光效果的ShaderMaterial
    // 创建一个新的ShaderMaterial，自定义材质可以让你完全控制物体的渲染方式
    const flowingLightMaterial = new THREE.ShaderMaterial({
      uniforms: {
        // uniforms是着色器中的全局变量，可以从JavaScript代码中动态更新它们的值
        color: { value: new THREE.Color(0x4f87ff) }, // 设置流光的颜色
        time: { value: 0.0 } // 时间变量，用于动态更新流光效果
      },
      vertexShader: `
        // 顶点着色器代码
        varying vec2 vUv; // 定义一个变化量(varying), 用于在顶点和片段着色器之间传递数据
        void main() {
          vUv = uv; // 将顶点的UV坐标传给片段着色器
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); // 计算顶点的最终位置
        }
      `,
      fragmentShader: `
        // 片段着色器代码
        uniform vec3 color; // 引用上面定义的uniform变量color
        uniform float time; // 引用上面定义的uniform变量time
        varying vec2 vUv; // 接收顶点着色器传递的UV坐标
        void main() {
          vec3 col = color; // 使用uniform变量中的颜色
          float alpha = sin(vUv.x * 10.0 + time) * 0.5 + 0.5; // 使用sin函数和time变量创建一个随时间变化的透明度效果
          alpha = mix(0.2, 1.0, alpha); // 调整alpha值, 使其在0.5到1.0之间变化
          gl_FragColor = vec4(col, alpha); // 设置最终的片段颜色，包含颜色和计算得到的透明度
        }
      `,
      transparent: true // 开启透明度，这允许物体显示透明效果
    });

    // 创建轨迹线
    //THREE.CatmullRomCurve3对象。这种类型的曲线可以用来创建平滑过渡的路径，经常用于动画中物体的移动、相机路径的定义等场景
    /* const path = new THREE.CatmullRomCurve3([ 
      new THREE.Vector3(-20, 0, 20), // 扩大点之间的距离
      new THREE.Vector3(-10, 10, 10),
      new THREE.Vector3(0, 0, 0),    // 中心点保持不变
      new THREE.Vector3(10, -10, 10),
      new THREE.Vector3(20, 0, 20),  // 扩大点之间的距离
      // 添加更多的控制点可以使曲线更长
      new THREE.Vector3(30, 10, -10), // 新增的控制点
      new THREE.Vector3(40, 0, 20)   // 新增的控制点
    ]); */
    // 创建一条直线路径
    const path = new THREE.CatmullRomCurve3([
      new THREE.Vector3(0, 200, -1234), // 直线的起点
      new THREE.Vector3(0, 200, ),   // 直线的中间点（实际上对于直线来说不是必需的，但可以保留以确保兼容性）
      new THREE.Vector3(0, 200, 1234)   // 直线的终点
    ]);
    const tubeGeometry = new THREE.TubeGeometry(path, 100, 20, 8, true); // 生成管道几何体 (path, 管道的分段数, 管道的半径, 管道截面的分段数, 管道是否闭合)
    const tubeMesh = new THREE.Mesh(tubeGeometry, flowingLightMaterial); // 创建网格
    tubeMesh.castShadow = true; // 使模型能够投射阴影
    tubeMesh.receiveShadow = true; // 使模型能够接收阴影
    scene.add(tubeMesh); // 添加到场景

    // 更新材质的时间值来创建流动效果
    const animate = () => {
      /* this.key ++;
      console.log(this.key); */
      requestAnimationFrame(animate);

      flowingLightMaterial.uniforms.time.value += 0.02;
      renderer.render(scene, camera);
    };

    animate();
  },

  }
}
</script>

<style lang='scss' scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #4f87ff;
}
.hello{
  width: 1000px;
  height: 800px;
}
.canvas{
  width: 100%;
  height: 100%;
  color: #ffe9d5;
}
</style>
